#include <tinx/printk.h>
#include <tinx/memory.h>
#include <tinx/task.h>
#include <tinx/buffer.h>
#include <tinx/debug.h>
#include <tinx/device.h>
#include <tinx/arena.h>
#include <tinx/assert.h>
#include <tinx/list.h>
#include <tinx/string.h>

static bdesc_t bdesc_table[3];

static idx_t hash(dev_t dev, idx_t block)
{
    return (dev ^ block) % HASH_COUNT;
}

static void hash_locate(buffer_t *buf)
{
    idx_t idx = hash(buf->dev, buf->block);
    list_append(&buf->desc->table[idx], &buf->hnode);
}

static void hash_remove(buffer_t *buf)
{
    if (buf->hnode.next)
    {
        list_remove(&buf->hnode);
    }
}

static bdesc_t *desc_get(size_t size)
{
    for (int i = 0; i < 3; i++)
    {
        bdesc_t *desc = &bdesc_table[i];
        if (desc->size == size)
            return desc;
    }
}

void buffer_alloc(bdesc_t *desc)
{
    u32 size = desc->size;
    u32 page = alloc_kpage(1);

    for (int i = 0, addr = page; i < 4; i++, addr += size)
    {
        // 分配一个 buffer_t
        buffer_t *buf = kmalloc(sizeof(buffer_t));

        // 初始化 buffer
        memset(buf, 0, sizeof(buffer_t));

        // 加入空闲列表
        list_append(&desc->free_list, &buf->rnode);

        // 初始化锁
        lock_init(&buf->lock);

        buf->data = (void *)addr;
        buf->desc = desc;
        desc->count++;
    }
}

buffer_t *get_from_hash_table(bdesc_t *desc, dev_t dev, idx_t block)
{
    list_t *list = &desc->table[hash(dev, block)];
    buffer_t *buf = NULL;

    for (list_node_t *node = list->head.next; true; node = node->next)
    {
        if (node == &list->tail)
            break;
        buffer_t *ptr = element_entry(buffer_t, hnode, node);
        if (ptr->dev == dev && ptr->block == block)
        {
            buf = ptr;
            break;
        }
    }

    if (!buf)
    {
        return buf;
    }

    if (buf->rnode.next)
    {
        assert(buf->count == 0);
        list_remove(&buf->rnode);
    }

    return buf;
}

buffer_t *get_free_buffer(bdesc_t *desc)
{
    task_t *task = running_task();

    list_node_t *node = NULL;
    list_t *list = &desc->free_list;
    buffer_t *buf;

    bool empty = list_empty(list);

    if (empty && desc->count < BUFFER_COUNT)
    {
        buffer_alloc(desc);
        empty = !empty;
    }

    if (!empty)
    {
        node = list_pop(list);
        goto rollback;
    }

    list = &desc->idle_list;

    while (!(node = list_pop(list)))
    {
        list_append(&desc->waiters, &task->node);
        task_block(task, TASK_BLOCKED);
    }

rollback:
    buf = element_entry(buffer_t, rnode, node);
    hash_remove(buf);
    return buf;
}

buffer_t *getblk(dev_t dev, idx_t block, size_t size)
{
    bdesc_t *desc = desc_get(size);
    buffer_t *buf = get_from_hash_table(desc, dev, block);

    if (buf)
    {
        buf->count++;
        return buf;
    }

    buf = get_free_buffer(desc);
    assert(buf);

    buf->block = block;
    buf->dev = dev;
    buf->count++;
    buf->vaild = false;

    hash_locate(buf);

    return buf;
}

buffer_t *bread(dev_t dev, idx_t block, size_t size)
{
    buffer_t *buf = getblk(dev, block, size);

    if (!buf->vaild)
    {
        lock_acquire(&buf->lock);

        int ret = device_request(
            dev,
            REQ_READ,
            buf->data,
            size / SECTOR_SIZE,
            block * BLOCK_SECTORS(size),
            0);
        assert(ret >= 0);

        buf->vaild = true;

        lock_release(&buf->lock);
    }
    return buf;
}

int bwrite(buffer_t *buf)
{
    if (buf->dirty)
    {
        lock_acquire(&buf->lock);

        size_t size = buf->desc->size;
        int ret = device_request(
            buf->dev,
            REQ_WRITE,
            buf->data,
            size / SECTOR_SIZE,
            buf->block * BLOCK_SECTORS(size),
            0);

        buf->dirty = false;

        assert(ret >= 0);
        lock_release(&buf->lock);
    }

    return EOK;
}

int brelse(buffer_t *buf)
{
    if (!buf)
    {
        return EOK;
    }

    int ret = bwrite(buf);

    buf->count--;

    if (!buf->count)
    {
        list_append(&buf->desc->idle_list, &buf->rnode);
    }

    return EOK;
}

void buffer_init()
{
    size_t size = 1024;

    for (int i  = 0; i < 3; i++)
    {
        bdesc_t *desc = &bdesc_table[i];
        desc->size = size;
        size <<= 1;

        list_init(&desc->free_list);
        list_init(&desc->idle_list);
        list_init(&desc->waiters);

        for (int idx = 0; idx < HASH_COUNT; idx++)
        {
            list_init(&desc->table[idx]);
        }
    }
}